using System;

using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Entity.Scene;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Core.Surface
{
    public class RayEngineMaterial
    {
        public string Name { get; set; }

        public ITexture DiffuseTexture;
        public ITexture BumpTexture;
        public ITexture NormalTexture;
        public ITexture GlossTexture;
        public ITexture SpecularTexture;
        public ITexture TransmittanceTexture;
        public ITexture AlphaTexture;

        protected MaterialInfo MaterialInfo;
        protected ITextureSampler TextureSampler;


        public RayEngineMaterial(MaterialInfo mi, ITextureSampler texSampler,
            Func<TextureType, MaterialInfo, ITexture> textureResolver)
        {
            this.Name = mi.Name.ToLower();
            this.MaterialInfo = mi;
            this.TextureSampler = texSampler;
            this.DiffuseTexture = textureResolver(TextureType.Diffuse, mi);
            this.SpecularTexture = textureResolver(TextureType.Specular, mi);
            this.BumpTexture = textureResolver(TextureType.Bump, mi);
            this.NormalTexture = textureResolver(TextureType.Normal, mi);
            this.GlossTexture = textureResolver(TextureType.Gloss, mi);
            this.TransmittanceTexture = textureResolver(TextureType.Refraction, mi);
            this.AlphaTexture = textureResolver(TextureType.Alpha, mi);
        }



        public virtual BaseBxdf GetBsdf(ref RayData ray, ref RayHit rayHit, ref Normal ng, ref Normal ns, ref UV texCoord, ref MediumInfo prevMedium, bool fromLight, float u0)
        {
            var textureData = SampleTextures(ref texCoord, ref prevMedium);
            textureData.Event = u0;

            return new MatteLambertBrdf(ref rayHit, ref ray, ref ng, ref ns, ref texCoord, MaterialInfo,textureData, fromLight);
        }

        private SurfaceTextureInfo textureData;

        protected SurfaceTextureInfo SampleTextures(ref UV texCoord, ref MediumInfo prevMedium)
        {
            if (textureData == null)
            {
                textureData = new SurfaceTextureInfo()
                {
                    PreviousMedium = prevMedium,
                    Medium = MaterialInfo.MediumInfo,
                    Exponent = MaterialInfo.Exponent,
                };
            }
            else
            {
                textureData.PreviousMedium.IoR = prevMedium.IoR;
                textureData.Exponent = MaterialInfo.Exponent;
                textureData.Medium.IoR = MaterialInfo.MediumInfo.IoR;
            }
            if (AlphaTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, AlphaTexture, out textureData.Alpha);
            if (DiffuseTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, DiffuseTexture, out textureData.Diffuse);
            if (SpecularTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, SpecularTexture, out textureData.Specular);
            if (BumpTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, BumpTexture, out textureData.Bump);
            if (GlossTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, GlossTexture, out textureData.Gloss);
            if (TransmittanceTexture != null)
                TextureSampler.Sample(texCoord.U, texCoord.V, TransmittanceTexture, out textureData.Transmittance);

            /*
              var bumpTex = scene.Query(meshMat, TextureType.Bump);
                if (bumpTex != null)
                {
                    var map = bumpTex;
                    var dudv = map.DUDV;
                    RgbSpectrumInfo bs;
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, bumpTex, out bs);

                    float b0 = bs.Filter();

                    UV uvdu = new UV(ii.TexCoords.U + dudv.U, ii.TexCoords.V);
                    texSampler.SampleTexture(uvdu.U, uvdu.V, bumpTex, out bs);

                    float bu = bs.Filter();

                    UV uvdv = new UV(ii.TexCoords.U, ii.TexCoords.V + dudv.V);
                    texSampler.SampleTexture(uvdv.U, uvdv.V, bumpTex, out bs);

                    float bv = bs.Filter();

                    float scale = 1.0f; //bm->GetScale();
                    bump = new Vector(scale * (bu - b0), scale * (bv - b0), 1f);

                    ii.Normal = new Normal(
                        v1.x * bump.x + v2.x * bump.y + geoNormal.x * bump.z,
                        v1.y * bump.x + v2.y * bump.y + geoNormal.y * bump.z,
                        v1.z * bump.x + v2.z * bump.y + geoNormal.z * bump.z).Normalize();
                }

                var nfScale = 2.0f;
                var nfIscale = 0.5f;
                var normTex = scene.Query(meshMat, TextureType.Normal);
                if (normTex != null)
                {
                    RgbSpectrumInfo color;
                    texSampler.SampleTexture(ii.TexCoords.U, ii.TexCoords.V, normTex, out color);
                    float x = nfScale * (color.c1 - nfIscale);
                    float y = nfScale * (color.c2 - nfIscale);
                    float z = nfScale * (color.c3 - nfIscale);

                    ii.Normal = new Normal(
                        v1.x * x + v2.x * y + geoNormal.x * z,
                        v1.y * x + v2.y * y + geoNormal.y * z,
                        v1.z * x + v2.z * y + geoNormal.z * z).Normalize();
                }
             */
            return textureData;
        }
    }

    public class GenericRayEngineMaterial<TBsdf> : RayEngineMaterial
        where TBsdf:BaseBxdf,new()
    {
        public GenericRayEngineMaterial(MaterialInfo mi, ITextureSampler texSampler, Func<TextureType, MaterialInfo, ITexture> textureResolver) : base(mi, texSampler, textureResolver)
        {
        }

        private TBsdf bsdf;

        public override BaseBxdf GetBsdf(ref RayData ray, ref RayHit rayHit, ref Normal ng, ref Normal ns, ref UV texCoord, ref MediumInfo prevMedium, bool fromLight, float u0)
        {
            var textureData = SampleTextures(ref texCoord, ref prevMedium);
            textureData.Event = u0;
            if (bsdf == null)
                bsdf = new TBsdf();
            bsdf.Init(ref rayHit, ref ray, ref ng, ref ns, ref texCoord, this.MaterialInfo, textureData, fromLight);
            return bsdf;
        }
    }
}